﻿namespace MyCompany.Models {
	using System;
	using System.Collections.Generic;
	using System.Diagnostics;

	using Fleck;

	using MyCompany.Core;
	using MyCompany.Interfaces;

	using SolidMvvm;

	using SolidMvvmInterfaces;

	/// <summary>
	///     A wrapper for a Fleck WebSocket server.
	/// </summary>
	public class LocalHostWebSocketServer : ModelBase, ILocalHostWebSocketServer {
		IList<IWebSocketConnection> webSocketConnections = new List<IWebSocketConnection>();

		WebSocketServer webSocketServer;

		/// <summary>Constructor</summary>
		/// <param name="localIPAddressWithoutWebSocketPrefix">For example, "10.0.1.147".</param>
		/// <param name="port">A local-host port different than the HTTP server.</param>
		/// <param name="messageReporter">
		///     Any message reporter. It will only be invoked on the owning thread,
		///     so assuming that it's the UI thread, it's safe to put the message in the GUI.
		/// </param>
		/// <param name="receivedTextDelegate">Called when text is received over the socket.</param>
		public LocalHostWebSocketServer(string localIPAddressWithoutWebSocketPrefix, int port, IMessageReporter messageReporter, ReceivedTextDelegate receivedTextDelegate) {
			try {
				MessageReporter = messageReporter;
				LocalIPAddressWithoutWebSocketPrefix = localIPAddressWithoutWebSocketPrefix;
				Port = port;
				ReceivedText = receivedTextDelegate;

				webSocketServer = new WebSocketServer(Utilities.GetFullWebSocketAddressWithPort(localIPAddressWithoutWebSocketPrefix, port));
				webSocketServer.Start(socket => {
					socket.OnOpen = () => {
						webSocketConnections.Add(socket);
						MessageReporter.ReportMessage("Socket connection " + socket.ConnectionInfo.Id + " at " + socket.ConnectionInfo.ClientIpAddress + " opened.", null, MessageType.Console);
					};
					socket.OnClose = () => {
						webSocketConnections.Remove(socket);
						MessageReporter.ReportMessage("Socket connection " + socket.ConnectionInfo.Id + " at " + socket.ConnectionInfo.ClientIpAddress + " closed.", null, MessageType.Console);
					};
					socket.OnBinary = bytes => {
						foreach (var webSocketConnection in webSocketConnections)
							webSocketConnection.Send(bytes);
						MessageReporter.ReportMessage("Byte array message: " + Conversions.ByteArrayToHexString(bytes), null, MessageType.Console);
					};
					socket.OnMessage = message => {
						ReceivedText(this, message);

						foreach (var webSocketConnection in webSocketConnections)
							webSocketConnection.Send(message);
						MessageReporter.ReportMessage("Text message: " + message, null, MessageType.Console);
					};
					socket.OnError = exception => MessageReporter.ReportMessage("Web-socket error: " + exception.Message, exception.ToString(), MessageType.Console);
				});

				MessageReporter.ReportMessage("WebSocket server listening at " + webSocketServer.Location + ".", null, MessageType.Console);
			}
			catch (Exception exception) {
				Cleaner.CleanConstructor(this, exception);
			}
		}

		public ReceivedTextDelegate ReceivedText { get; private set; }

		public override void Dispose() {
			GC.SuppressFinalize(this);

			var disposeErrors = String.Empty;

			if (webSocketConnections != null)
				webSocketConnections.Clear();
			webSocketConnections = null;

			if (webSocketServer != null)
				try {
					webSocketServer.Dispose();
				}
				catch (Exception exception) {
					disposeErrors += exception.Message + "\n";
				}
			webSocketServer = null;

			MessageReporter = null;
			LocalIPAddressWithoutWebSocketPrefix = null;
			Port = -1;
			ReceivedText = null;

			try {
				base.Dispose();
			}
			catch (Exception exception) {
				disposeErrors += exception.Message + "\n";
			}

			if (disposeErrors != String.Empty)
				throw new CleanupException(disposeErrors.Trim(), null);
		}

		public IMessageReporter MessageReporter { get; private set; }

		public string LocalIPAddressWithoutWebSocketPrefix { get; private set; }

		public int Port { get; private set; }

		public void SendText(string message) {
			foreach (var webSocketConnection in webSocketConnections)
				webSocketConnection.Send(message);
		}

		public void SendBytes(byte[] bytes) {
			foreach (var webSocketConnection in webSocketConnections)
				webSocketConnection.Send(bytes);
		}

#if DEBUG
		~LocalHostWebSocketServer() {
			Debugger.Break(); // Who forgot to dispose the server?
		}
#endif
	}
}
